項目60 オブジェクトに対して反復処理する方法を知る
問題点
TypeScriptは構造的型付けであるがゆえ、オブジェクトを反復処理する際に、型エラーが発生することがある ABC型は代入可能であれば、a,b,c以外のプロパティを持つことが可能であるため、TypeScriptはkの型を広げて捉えstringと推論する
code:ts
interface ABC {a: string;b: string;c: number;}
function foo(abc: ABC) {
for (const kStr in abc) {
let k = kStr as keyof ABC;
// ^? let k: keyof ABC("a" | "b" | "c"に相当)
// ^? const v: string | number
}
}
for - in内で、k = kStr as keyof ABCとすることでkの型は明確に推論されるが、実行時は異なる型の値を持ったプロパティが存在するケースが有り得るため、型と実際の値が一致せず混乱を生みやすい
code:ts
function foo(abc: ABC) {
const arr = []
for (const kStr in abc) {
let k = kStr as keyof ABC
}
return arr
}
const x = {a: '1', b: '2', c: 3, d: new Date()}
// function foo(abc: ABC): (string | number)[]
オブジェクトを反復処理する際、プロパティ汚染を考慮して、キーの型がstringになる Object.prototypeに定義されたプロパティは他のすべてのオブジェクトに継承される
継承されたプロパティはfor - inで列挙されるため、あらゆるキーに対応できるようstringとして推論される
ただ、Object.entriesは継承されたプロパティを除外するかつ、キーと値を一緒に反復するため安全
code:ts
function foo(abc: ABC) {
for (const k, v of Object.entries(abc)) { // ^? const k: string
console.log(v);
// ^? const v: any
}
}
安全な方法
扱うキーが明確になっている場合は、型アサーションを使ってオブジェクトを反復処理する
変数abc自体は、未知のプロパティを持つ可能性があるが、keys でアクセス対象を制限しているので安全
code:ts
function foo(abc: ABC) {
for (const k of keys) {
// ^? const k: "a" | "b" | "c"
// ^? const v: string | number
}
}
オブジェクトではなく反復処理が容易なMapを使う
code:ts
const m = new Map([
// ^? const m: Map<string, string>
]);
for (const k, v of m.entries()) { // ^? const k: string
console.log(v);
// ^? const v: string
}